home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 5 / MacMania 5.toast / / Internet software / NewsWatcher / NW Source / Source / full.c < prev    next >
Text File  |  1997-01-09  |  29KB  |  1,000 lines

  1. /*----------------------------------------------------------------------------
  2.  
  3.     full.c
  4.  
  5.     This module handles tasks involving the full group list.
  6.     
  7.     Copyright © 1994-1997, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13.  
  14. #include "glob.h"
  15. #include "full.h"
  16. #include "qsort.h"
  17. #include "dialog.h"
  18. #include "news.h"
  19. #include "newswatcher.h"
  20. #include "menus.h"
  21. #include "status.h"
  22. #include "wind.h"
  23. #include "group.h"
  24. #include "text.h"
  25. #include "memutil.h"
  26. #include "strutil.h"
  27. #include "windutil.h"
  28. #include "subscribe.h"
  29. #include "fileutil.h"
  30. #include "biglist.h"
  31.  
  32.  
  33.  
  34. #define kMustCloseBeforeRebuildDlg        150
  35.  
  36.  
  37.  
  38. static Handle gSortGroupNames;            /* handle to group name strings during sorting */
  39.  
  40.  
  41.  
  42. /*----------------------------------------------------------------------------
  43.     FindGroupIndex 
  44.     
  45.     Find the index of a group in gGroupNameOffsets.
  46.  
  47.     Entry:    name = group name.
  48.             gFullGroupNameOffsets sorted in increasing order by group name.
  49.  
  50.     Exit:    function result = index in gGroupNameOffsets of group, 
  51.                 or -1 if not found.
  52. ----------------------------------------------------------------------------*/
  53.  
  54. long FindGroupIndex (char *name)
  55. {
  56.     long low = 0;
  57.     long high = gNumGroups-1;
  58.     long mid;
  59.     short compare;
  60.     long nameOffset;
  61.     
  62.     while (low <= high) {
  63.         mid = (low + high) >> 1;
  64.         nameOffset = (*gGroupNameOffsets)[mid] & 0x7fffffff; /* mask off sign bit
  65.             which might be set by DoCheckForDeletedGroups - see below. */
  66.         compare = strcmp(name, *gGroupNames + nameOffset);
  67.         if (compare == 0) {
  68.             return mid;
  69.         } else if (compare < 0) {
  70.             high = mid-1;
  71.         } else {
  72.             low = mid+1;
  73.         }
  74.     }
  75.     return -1;        
  76. }
  77.  
  78.  
  79.  
  80. /*----------------------------------------------------------------------------
  81.     FindGroupOffset 
  82.     
  83.     Find the offset of a group name in gGroupNames.
  84.  
  85.     Entry:    name = group name.
  86.             gFullGroupNameOffsets sorted in increasing order by group name.
  87.  
  88.     Exit:    function result = offset in gGroupNames of group, or -1 if not found.
  89. ----------------------------------------------------------------------------*/
  90.  
  91. long FindGroupOffset (char *name)
  92. {
  93.     long index;
  94.  
  95.     index = FindGroupIndex(name);
  96.     return index == -1 ? -1 : (*gGroupNameOffsets)[index];
  97. }
  98.  
  99.  
  100.  
  101. /*----------------------------------------------------------------------------
  102.     GroupCompare 
  103.     
  104.     The group comparison routine used in the calls to FastQSort in 
  105.     SortGroupNameOffsets and CheckForNewGroups below. It does a 
  106.     simple string compare and gives time to background applications.
  107.     
  108.     Entry:    p = pointer to first group name offset.
  109.             q = pointer to second group name offset.
  110.             gSortGroupNames = handle to group names block.
  111.             
  112.     Exit:    function result = error code.
  113.             *result
  114.                 < 0 if first group name < second group name.
  115.                 = 0 if first group name = second group name.
  116.                 > 0 if first group name > second group name.
  117. ----------------------------------------------------------------------------*/
  118.  
  119. static OSErr GroupCompare (long *p, long *q, short *result)
  120. {
  121.     OSErr err;
  122.     static short counter = 0;
  123.  
  124.     if ((++counter & 0x1f) == 0) {
  125.         err = GiveTime(false);
  126.         if (err != noErr) return err;
  127.         counter = 0;
  128.     }
  129.  
  130.     *result = strcmp(*gSortGroupNames + *p, *gSortGroupNames + *q);
  131.     return noErr;
  132. }
  133.  
  134.  
  135.  
  136. /*----------------------------------------------------------------------------
  137.     SortFullGroupList 
  138.     
  139.     Sort a full group list.
  140.     
  141.     Entry:    groupNameOffsets = handle to array of group name offsets.
  142.             numGroups = number of groups in array.
  143.             groupNames = handle to group names block.
  144.     
  145.     Exit:    function result = error code.
  146.             groupNameOffsets array sorted into increasing order by group name.
  147. ----------------------------------------------------------------------------*/
  148.  
  149. static OSErr SortFullGroupList (long **groupNameOffsets, long numGroups, Handle groupNames)
  150. {
  151.     OSErr err = noErr;
  152.     char state;
  153.  
  154.     if (numGroups > 0) {
  155.         err = DisplayStatusMessageNumber(kStrSortingStatusMsg);
  156.         if (err != noErr) return err;
  157.         gSortGroupNames = groupNames;
  158.         state = MyHGetState(groupNameOffsets);
  159.         MyHLock(groupNameOffsets);
  160.         err = FastQSort(*groupNameOffsets, numGroups, sizeof(long), 
  161.             (SortCmpFunction)GroupCompare);
  162.         MyHSetState(groupNameOffsets, state);
  163.         if (err != noErr) return err;
  164.     }
  165.     return noErr;
  166. }
  167.  
  168.  
  169.  
  170. /*----------------------------------------------------------------------------
  171.     ReadGroupsFromPrefs 
  172.     
  173.     Read the full group list stored on the preferences file.
  174.     
  175.     Entry:    fSpec = pointer to file spec of prefs file.
  176.             prefsInDataFork = true if prefs are in data fork of prefs file
  177.                 and the full group list follows the prefs. False if the
  178.                 prefs are in the resource fork and the data fork contains
  179.                 only the full group list.
  180.             prefsVersion = version number of prefs file.
  181.  
  182.     Exit:    function result = error code.
  183. ----------------------------------------------------------------------------*/
  184.  
  185. OSErr ReadGroupsFromPrefs (FSSpec *fSpec, Boolean prefsInDataFork, 
  186.     unsigned long prefsVersion)
  187. {
  188.     OSErr err = noErr;
  189.     short fRefNum = 0;
  190.     Boolean needSort = false;
  191.     char *p, *pEnd;
  192.     char *prevName = nil;
  193.     char *curName;
  194.     long *x;
  195.     long groupNamesSize, i;
  196.     char state;
  197.  
  198.     err = DisplayStatusMessageNumber(kStrReadingStatusMsg);
  199.     if (err != noErr) goto exit;
  200.         
  201.     err = FSpOpenDF(fSpec, fsRdPerm, &fRefNum);
  202.     if (err != noErr) return noErr;
  203.  
  204.     /* Read the saved group names. */
  205.     
  206.     err = GetEOF(fRefNum, &groupNamesSize);
  207.     if (err != noErr) goto exit;
  208.     if (prefsInDataFork) {
  209.         groupNamesSize -= sizeof(TPrefRec);
  210.         if (groupNamesSize < 0) {
  211.             err = eofErr;
  212.             goto exit;
  213.         }
  214.         err = SetFPos(fRefNum, fsFromStart, sizeof(TPrefRec));
  215.         if (err != noErr) goto exit;
  216.     }
  217.     err = MyNewHandle(groupNamesSize, &gGroupNames);
  218.     if (err != noErr) goto exit;
  219.     state = MyHGetState(gGroupNames);
  220.     MyHLock(gGroupNames);
  221.     err = FSRead(fRefNum, &groupNamesSize, *gGroupNames);
  222.     MyHSetState(gGroupNames, state);
  223.     if (err != noErr) goto exit;
  224.     MyFSClose(fRefNum, nil);
  225.     fRefNum = 0;
  226.     
  227.     /* Special case the Cornell version 2.0d15-CU, which put 
  228.        a 32 byte authorization username and 32 bytes of zero at 
  229.        the end of the NU 2.0d14 prefs, which in any NU version 
  230.        of NW shows up as the first 64 bytes of what we think 
  231.        is the full group list! (Yes, yuck). 
  232.        
  233.        First we check prefsVersion to see if it is 2.0d14. If 
  234.        it is, we then check byte 32 of the full group list. If 
  235.        byte 32 is 0, we assume this is a Cornell prefs file. In 
  236.        this case, we copy the first 32 bytes (the Cornell authorization 
  237.        username) to gPrefs.authUsername (our authorization username). 
  238.        We discard the next unused 32 zero bytes. 
  239.        
  240.        Note that in NU prefs files, a 0 byte never appears in the
  241.        full group list, so theoretically there is no chance of
  242.        confusing an NU prefs file with a CU prefs file.
  243.        
  244.        This makes NU NewsWatcher 2.0d27 and later understand and 
  245.        properly convert 2.0d15-CU prefs files. The reverse will never 
  246.        work - 2.0d15-CU is not able to make any sense of any NU version 
  247.        prefs file.
  248.     */
  249.  
  250.     if (prefsVersion == 0x02002014 && groupNamesSize >= 64 &&
  251.         *(*gGroupNames+32) == 0) 
  252.     {
  253.         BlockMoveData(*gGroupNames, gPrefs.authUsername, 32);
  254.         groupNamesSize -= 64;
  255.         BlockMoveData(*gGroupNames + 64, *gGroupNames, groupNamesSize);
  256.         MySetHandleSize(gGroupNames, groupNamesSize);
  257.         gFullGroupListDirty = true;
  258.     }    
  259.     
  260.     /* Check to make certain the last group name ends in CR. If not, strip
  261.        any trailing junk. */
  262.        
  263.     p = *gGroupNames + groupNamesSize - 1;
  264.     while (p >= *gGroupNames && *p != CR) p--;
  265.     p++;
  266.     if (p < *gGroupNames + groupNamesSize) {
  267.         groupNamesSize = p - *gGroupNames;
  268.         MySetHandleSize(gGroupNames, groupNamesSize);
  269.         gFullGroupListDirty = true;
  270.         if (p == *gGroupNames) return noErr;
  271.     }
  272.     
  273.     /* Walk through the gGroupNames buffer. Count the number of groups. 
  274.        Change all CR to 0. Check to see if the groups are already sorted
  275.        (they should be). */
  276.        
  277.     p = *gGroupNames;
  278.     pEnd = p + groupNamesSize;
  279.     gNumGroups = 0;
  280.     while (p < pEnd) {
  281.         curName = p;
  282.         while (*p != CR) p++;
  283.         *p++ = 0;
  284.         gNumGroups++;
  285.         needSort = needSort || (prevName != nil && strcmp(prevName, curName) > 0);
  286.         prevName = curName;
  287.     }
  288.     
  289.     /* Allocate and initialize the group name offsets array. */
  290.     
  291.     err = MyNewHandle(gNumGroups*sizeof(long), &gGroupNameOffsets);
  292.     if (err != noErr) goto exit;
  293.     
  294.     for (i = 0, x = *gGroupNameOffsets, p = *gGroupNames; i < gNumGroups; i++, x++) {
  295.         *x = p - *gGroupNames;
  296.         p += strlen(p) + 1;
  297.     }
  298.     
  299.     /* If necessary, sort the group names offset array. */
  300.  
  301.     if (needSort) {
  302.         gFullGroupListDirty = true;
  303.         err = SortFullGroupList(gGroupNameOffsets, gNumGroups, gGroupNames);
  304.         if (err != noErr) goto exit;
  305.     }
  306.     
  307.     return noErr;
  308.     
  309. exit:
  310.  
  311.     MyDisposeHandle(gGroupNames);
  312.     if (fRefNum != 0) MyFSClose(fRefNum, nil);
  313.     gNumGroups = 0;
  314.     return err;
  315. }
  316.  
  317.  
  318.  
  319. /*----------------------------------------------------------------------------
  320.     AdjustFullGroupListChildWindows
  321.  
  322.     This function must be called whenever the full group list changes. It locates
  323.     all the open child subject list windows. If a group has been deleted, any
  324.     associated open child subject list window is closed. If a group still exists,
  325.     the "parentGroup" backpointer in the child window's TWindow info is adjusted
  326.     to point to the new location of the parent group in the group name offsets array
  327.     gGroupNameOffsets.
  328.     
  329.     Exit:    function result = error code.
  330. ----------------------------------------------------------------------------*/
  331.  
  332. static OSErr AdjustFullGroupListChildWindows (void)
  333. {
  334.     TWindow **info, **childInfo;
  335.     TChild **childList, **prevChildList;
  336.     WindowPtr childWindow;
  337.     CStr255 groupName;
  338.     long index;
  339.     OSErr err = noErr;
  340.     
  341.     gFullGroupListDirty = true;
  342.     if (gFullGroupWindow == nil) return noErr;
  343.     info = (TWindow**)GetWRefCon(gFullGroupWindow);
  344.     childList = (**info).childList;
  345.     prevChildList = nil;
  346.     while (childList != nil) {
  347.         childWindow = (**childList).childWindow;
  348.         childInfo = (TWindow**)GetWRefCon(childWindow);
  349.         strcpy(groupName, *gGroupNames + (**childInfo).groupNameOffset);
  350.         index = FindGroupIndex(groupName);
  351.         if (index == -1) {
  352.             /* Group has been deleted. Close the child window. */
  353.             err = DoClose(childWindow);
  354.             if (err != noErr) return err;
  355.             if (prevChildList == nil) {
  356.                 childList = (**info).childList;
  357.             } else {
  358.                 childList = (**prevChildList).next;
  359.             }
  360.         } else {
  361.             /* Group still exists. Update the parentGroup backpointer. */
  362.             (**childInfo).parentGroup = index;
  363.             prevChildList = childList;
  364.             childList = (**childList).next;
  365.         }
  366.     }
  367.     return noErr;
  368. }
  369.  
  370.  
  371.  
  372. /*----------------------------------------------------------------------------
  373.     UpdateFullGroupWindow 
  374.     
  375.     Make sure that the Full Group List window corresponds to the changed 
  376.     full group list. It must be called whenever the full group list changes.
  377.     
  378.     Exit:    function result = error code.
  379. ----------------------------------------------------------------------------*/
  380.  
  381. static OSErr UpdateFullGroupWindow (void)
  382. {
  383.     TWindow **info;
  384.     GrafPtr port;
  385.     OSErr err = noErr;
  386.  
  387.     GetPort(&port);
  388.     
  389.     err = AdjustFullGroupListChildWindows();
  390.     if (err != noErr) return err;
  391.     if (gFullGroupWindow != nil) {
  392.         info = (TWindow**)GetWRefCon(gFullGroupWindow);
  393.         (**info).groupNameOffsets = gGroupNameOffsets;
  394.         (**info).numGroups = gNumGroups;
  395.         err = InitializeGroupList(gNumGroups, (**info).groupList, kFullGroup);
  396.         if (err != noErr) return err;
  397.         BigLSelectOnlyOne((**info).groupList, 0);
  398.         SetPort(gFullGroupWindow);
  399.         InvalRect(&gFullGroupWindow->portRect);
  400.     }
  401.     
  402.     SetPort(port);
  403.     return noErr;
  404. }
  405.  
  406.  
  407.  
  408. /*----------------------------------------------------------------------------
  409.     MergeNewGroupsIntoFullGroupList 
  410.     
  411.     Merge new groups into the full group list. Both lists must be sorted on 
  412.     entry. The full group list remains sorted on exit.
  413.     
  414.     Entry:    newGroupNameOffsets = handle to new groups array of group
  415.                 name offsets.
  416.             numNew = number of new groups.
  417.     
  418.     Exit:    function result = error code.
  419. ----------------------------------------------------------------------------*/
  420.  
  421. static OSErr MergeNewGroupsIntoFullGroupList (long **newGroupNameOffsets, long numNew)
  422. {
  423.     long numLeftToInsert, numToMoveUp;
  424.     long *fullListPtr, *newListPtr;
  425.     char *newName;
  426.     OSErr err = noErr;
  427.  
  428.     gNumGroups += numNew;
  429.     err = MySetHandleSize(gGroupNameOffsets, gNumGroups*sizeof(long));
  430.     if (err != noErr) return err;
  431.     
  432.     numLeftToInsert = numNew;
  433.     fullListPtr = *gGroupNameOffsets + gNumGroups - numNew - 1;
  434.     newListPtr = *newGroupNameOffsets + numNew - 1;
  435.     while (numLeftToInsert > 0) {
  436.         newName = *gGroupNames + *newListPtr;
  437.         numToMoveUp = 0;
  438.         while (fullListPtr >= *gGroupNameOffsets &&
  439.             strcmp(newName, *gGroupNames + *fullListPtr) <= 0) 
  440.         {
  441.             fullListPtr--;
  442.             numToMoveUp++;
  443.         }
  444.         if (numToMoveUp > 0)
  445.             BlockMoveData(fullListPtr + 1, fullListPtr + numLeftToInsert + 1, 
  446.                 numToMoveUp*sizeof(long));
  447.         BlockMoveData(newListPtr, fullListPtr + numLeftToInsert, sizeof(long));
  448.         newListPtr--;
  449.         numLeftToInsert--;
  450.     }
  451.     
  452.     return noErr;
  453. }
  454.  
  455.  
  456.  
  457. /*----------------------------------------------------------------------------
  458.     CheckForNewGroups 
  459.     
  460.     Check for any new groups created since the last time we checked.
  461.  
  462.     Exit:    function result = error code.
  463.             *newGroupNameOffsets = handle to array of group name offsets for the
  464.                 new groups, or nil if numNewGroups == 0.
  465.             *numNewGroups = number of new groups.
  466. ----------------------------------------------------------------------------*/
  467.  
  468. OSErr CheckForNewGroups (long ***newGroupNameOffsets, long *numNewGroups)
  469. {
  470.     OSErr err = noErr;
  471.     short len, nameWidth;
  472.     long numGroups, numNew, i;
  473.     Handle strings = nil;
  474.     char *p, *q;
  475.     long offset, savedGroupNamesSize;
  476.     long **newOffsets = nil;
  477.     long *x;
  478.     CStr255 groupName;
  479.     GrafPtr port;
  480.     char state;
  481.     WindowPtr wind;
  482.     TWindow **info;
  483.     Handle unsubscribed;
  484.     
  485.     GetPort(&port);
  486.     savedGroupNamesSize = GetHandleSize(gGroupNames);
  487.     
  488.     err = DisplayStatusMessageNumber(kStrCheckingNewStatusMsg);
  489.     if (err != noErr) goto exit;
  490.         
  491.     /* Get the new group names from the server. */
  492.     
  493.     err = GetGroupNames(gPrefs.groupCheckTime, &strings, &numGroups);
  494.     if (err != noErr) goto exit;
  495.     
  496.     /* Check for duplicates. Filter out the groups which are already present in
  497.        the full group list. */
  498.  
  499.     numNew = 0;
  500.     for (i = 0, p = *strings, q = *strings; i < numGroups; i++) {
  501.         len = strlen(q);
  502.         if (FindGroupIndex(q) == -1) {
  503.             strcpy(p, q);
  504.             p += len+1;
  505.             numNew++;
  506.         }
  507.         q += len+1;
  508.     }
  509.     
  510.     MySetHandleSize(strings, p - *strings);
  511.     
  512.     if (numNew == 0) {
  513.         MyDisposeHandle(strings);
  514.         *numNewGroups = 0;
  515.         *newGroupNameOffsets = nil;
  516.         GetDateTime(&gPrefs.groupCheckTime);
  517.         SetPort(port);
  518.         return noErr;
  519.     }
  520.     
  521.     /* Append the new group names to the end of gGroupNames. */
  522.     
  523.     offset = savedGroupNamesSize;
  524.     err = MyHandAndHand(strings, gGroupNames);
  525.     if (err != noErr) goto exit;
  526.     
  527.     /* Allocate and initialize the array of offsets for the new groups. */
  528.     
  529.     err = MyNewHandle(numNew * sizeof(long), &newOffsets);
  530.     if (err != noErr) goto exit;
  531.     
  532.     for (i = 0, x = *newOffsets, p = *gGroupNames + offset; i < numNew; i++, x++) {
  533.         *x = p - *gGroupNames;
  534.         p += strlen(p) + 1;
  535.     }
  536.     
  537.     /* Sort the new offsets array. */
  538.     
  539.     gSortGroupNames = gGroupNames;
  540.     state = MyHGetState(newOffsets);
  541.     MyHLock(newOffsets);
  542.     err = FastQSort(*newOffsets, numNew, sizeof(long), (SortCmpFunction)GroupCompare);
  543.     MyHSetState(newOffsets, state);
  544.     if (err != noErr) goto exit;
  545.     
  546.     /* Merge the new groups into the full group list. */
  547.  
  548.     err = MergeNewGroupsIntoFullGroupList(newOffsets, numNew);
  549.     if (err != noErr) goto exit;
  550.     
  551.     err = UpdateFullGroupWindow();
  552.     if (err != noErr) goto exit;
  553.     
  554.     /* Update the last new groups check date and time. */
  555.     
  556.     GetDateTime(&gPrefs.groupCheckTime);
  557.     
  558.     /* Check to see if one of the new group names is now the widest group 
  559.        name in the full group list window. */
  560.     
  561.     SetPort(gFullGroupWindow);
  562.     if (gPrefs.maxGroupNameWidth > 0) {
  563.         for (i = 0; i < numNew; i++) {
  564.             strcpy(groupName, *gGroupNames + (*newOffsets)[i]); 
  565.             nameWidth = TextWidth(groupName, 0, strlen(groupName));
  566.             if (nameWidth > gPrefs.maxGroupNameWidth) gPrefs.maxGroupNameWidth = nameWidth;
  567.         }
  568.     }
  569.     
  570.     /* Add each new group name to the unsubscribed list of each open user group
  571.        list window. */
  572.        
  573.     wind = FrontWindow();
  574.     while (wind != nil) {
  575.         if (GetMyWindowKind(wind) == kGroup) {
  576.             info = (TWindow**)GetWRefCon(wind);
  577.             unsubscribed = (**info).unsubscribed;
  578.             if (unsubscribed != nil) {
  579.                 for (i = 0; i < numNew; i++) {
  580.                     strcpy(groupName, *gGroupNames + (*newOffsets)[i]);
  581.                     err = AddGroupToUnsubscribedList(groupName, unsubscribed);
  582.                     if (err != noErr) goto exit;
  583.                 }
  584.             }
  585.         }
  586.         wind = (WindowPtr)((WindowPeek)wind)->nextWindow;
  587.     }
  588.     
  589.     /* Return. */
  590.     
  591.     *newGroupNameOffsets = newOffsets;
  592.     *numNewGroups = numNew;
  593.     SetPort(port);
  594.     return noErr;
  595.     
  596. exit:
  597.  
  598.     MyDisposeHandle(strings);
  599.     MyDisposeHandle(newOffsets);
  600.     MySetHandleSize(gGroupNames, savedGroupNamesSize);
  601.     SetPort(port);
  602.     return err;
  603. }
  604.  
  605.  
  606.  
  607. /*----------------------------------------------------------------------------
  608.     DoCheckForNewGroups
  609.  
  610.     Handle the "Check for New Groups" command.
  611.     
  612.     Exit:    function result = error code.
  613. ----------------------------------------------------------------------------*/
  614.  
  615. OSErr DoCheckForNewGroups (void)
  616. {
  617.     long **newOffsets;
  618.     long numGroups;
  619.     WindowPtr wind;
  620.     OSErr err = noErr;
  621.  
  622.     err = CheckForNewGroups(&newOffsets, &numGroups);
  623.     if (err != noErr) return err;
  624.     if (numGroups > 0) {
  625.         return MakeNewGroupsWindow(newOffsets, numGroups, &wind);
  626.     } else {
  627.         return noErr;
  628.     }
  629. }
  630.  
  631.  
  632.  
  633. /*----------------------------------------------------------------------------
  634.     DoCheckForDeletedGroups 
  635.     
  636.     Handle the "Check for Deleted Groups" command.
  637.     
  638.     Exit:    function result = error code.
  639. ----------------------------------------------------------------------------*/
  640.  
  641. OSErr DoCheckForDeletedGroups (void)
  642. {
  643.     Handle strings = nil;
  644.     Handle deleted = nil;
  645.     long deletedNext, deletedAllocated, offset;
  646.     long numDel, numToMoveDown;
  647.     short len, nameWidth;
  648.     long numGroups, i, index, nameOffset;
  649.     long *x, *prev, *cur, *curEnd;
  650.     char *p;
  651.     OSErr err = noErr;
  652.     CStr255 groupName;
  653.     GrafPtr port;
  654.     Str255 title;
  655.     WindowPtr wind;
  656.     long groupNamesSize, memNeeded;
  657.     Handle tempGroupNames = nil;
  658.     Boolean fullGroupListMovedToTempMem = false;
  659.     Boolean savedCriticalSeq;
  660.     
  661.     GetPort(&port);
  662.  
  663.     err = DisplayStatusMessageNumber(kStrCheckingDelStatusMsg);
  664.     if (err != noErr) goto exit;
  665.     
  666.     /* Check available memory. If we don't have at least 1.25 times the size
  667.        of the current full group list available in the application heap,
  668.        try to move the current full group list into temporary memory to
  669.        make room to get the new full group list. */
  670.        
  671.     if (gGroupNames != nil) {
  672.         groupNamesSize = MyGetHandleSize(gGroupNames);
  673.         memNeeded = groupNamesSize;
  674.         memNeeded += memNeeded >> 2;
  675.         if (!MemoryAvailable(memNeeded)) {
  676.             if (HaveModernTempMemory()) {
  677.                 err = MyTempNewHandle(groupNamesSize, &tempGroupNames);
  678.                 if (err != noErr) goto exit;
  679.                 BlockMoveData(*gGroupNames, *tempGroupNames, groupNamesSize);
  680.                 MyDisposeHandle(gGroupNames);
  681.                 gGroupNames = tempGroupNames;
  682.                 fullGroupListMovedToTempMem = true;
  683.             } else {
  684.                 return memFullErr;
  685.             }
  686.         }
  687.     }
  688.     
  689.     /* Get a list of all group names from the server. */
  690.     
  691.     err = GetGroupNames(0, &strings, &numGroups);
  692.     if (err != noErr) goto exit;
  693.     
  694.     /* Mark all the groups in the full group list by setting the sign bit
  695.        in each element of the group name offsets array. Then walk the
  696.        fresh full group list we just got from the server and clear the
  697.        sign bit for each of the groups which still exist. This leaves just
  698.        the deleted groups marked with the sign bit set. */
  699.        
  700.     for (i = 0, x = *gGroupNameOffsets; i < gNumGroups; i++, x++) *x |= 0x80000000;
  701.     
  702.     for (i = 0, offset = 0; i < numGroups; i++) {
  703.         err = GiveTime(false);
  704.         if (err != noErr) goto exit;
  705.         p = *strings + offset;
  706.         index = FindGroupIndex(p);
  707.         if (index != -1) (*gGroupNameOffsets)[index] &= 0x7fffffff;
  708.         offset += strlen(p) + 1;
  709.     }
  710.     
  711.     MyDisposeHandle(strings);
  712.     strings = nil;
  713.     
  714.     /* If we moved the full group list to temp mem, copy it back to real mem. */
  715.     
  716.     if (fullGroupListMovedToTempMem) {
  717.         BeginCriticalMemorySequence(&savedCriticalSeq);
  718.         MyNewHandle(groupNamesSize, &gGroupNames);
  719.         BlockMoveData(*tempGroupNames, *gGroupNames, groupNamesSize);
  720.         MyDisposeHandle(tempGroupNames);
  721.         EndCriticalMemorySequence(savedCriticalSeq);
  722.         fullGroupListMovedToTempMem = false;
  723.     }
  724.     
  725.     /* Allocate a buffer to hold the names of the deleted groups for display
  726.        to the user. */
  727.     
  728.     err = MyNewHandle(0, &deleted);
  729.     if (err != noErr) goto exit;
  730.     deletedNext = deletedAllocated = 0;
  731.     
  732.     /* Walk the full group list. Copy the names of the deleted groups to the 
  733.        buffer. Also check to see if the group with the widest name in the full
  734.        group list window has been deleted. */
  735.     
  736.     SetPort(gFullGroupWindow);
  737.     numDel = 0;
  738.     for (i = 0; i < gNumGroups; i++) {
  739.         x = *gGroupNameOffsets + i;
  740.         if (*x < 0) {
  741.             nameOffset = *x & 0x7fffffff;
  742.             err = GiveTime(false);
  743.             if (err != noErr) goto exit;
  744.             numDel++;
  745.             strcpy(groupName, *gGroupNames + nameOffset);
  746.             len = strlen(groupName);
  747.             if (deletedNext + len + 1 > deletedAllocated) {
  748.                 deletedAllocated += 1000;
  749.                 err = MySetHandleSize(deleted, deletedAllocated);
  750.                 if (err != noErr) goto exit;
  751.             }
  752.             strcpy(*deleted + deletedNext, groupName);
  753.             deletedNext += len+1;
  754.             *(*deleted + deletedNext - 1) = CR;
  755.             if (gPrefs.maxGroupNameWidth > 0) {
  756.                 nameWidth = TextWidth(groupName, 0, len);
  757.                 if (nameWidth >= gPrefs.maxGroupNameWidth) gPrefs.maxGroupNameWidth = 0;
  758.             }
  759.         }
  760.     }
  761.     
  762.     /* If there aren't any deleted groups, issue a note message and return. */
  763.     
  764.     if (numDel == 0) {
  765.         MyDisposeHandle(deleted);
  766.         NoteMessageNumber(kStrNoDelGroups);
  767.         return noErr;
  768.     }
  769.     
  770.     MySetHandleSize(deleted, deletedNext);
  771.     
  772.     /* Remove the deleted groups from the full group list. */
  773.     
  774.     prev = *gGroupNameOffsets;
  775.     cur = *gGroupNameOffsets;
  776.     curEnd = cur + gNumGroups;
  777.     numDel = 0;
  778.     while (cur < curEnd) {
  779.         numToMoveDown = 0;
  780.         while (cur < curEnd && *cur >= 0) {
  781.             cur++;
  782.             numToMoveDown++;
  783.         }
  784.         if (numDel > 0 && numToMoveDown > 0)
  785.             BlockMoveData(prev + numDel, prev, numToMoveDown*sizeof(long));
  786.         prev += numToMoveDown;
  787.         if (cur < curEnd) numDel++;
  788.         cur++;
  789.     }
  790.     
  791.     /* Update the full group list window, display the deleted group names to the
  792.        user in a text window, and return. */
  793.     
  794.     gNumGroups -= numDel;
  795.     MySetHandleSize(gGroupNameOffsets, gNumGroups*sizeof(long));
  796.     err = UpdateFullGroupWindow();
  797.     if (err != noErr) goto exit;
  798.     GetPString(kStrDelGroupsWindTitle, title);
  799.     err = MakeNewTextWindow(title, 0, nil, deleted, &wind);
  800.     if (err != noErr) goto exit;
  801.     
  802.     MyDisposeHandle(deleted);
  803.     SetPort(port);
  804.     return noErr;
  805.     
  806. exit:
  807.  
  808.     MyDisposeHandle(strings);
  809.     MyDisposeHandle(deleted);
  810.     SetPort(port);
  811.     for (i = 0, x = *gGroupNameOffsets; i < gNumGroups; i++, x++) *x &= 0x7fffffff;
  812.     if (fullGroupListMovedToTempMem) {
  813.         BeginCriticalMemorySequence(&savedCriticalSeq);
  814.         MyNewHandle(groupNamesSize, &gGroupNames);
  815.         BlockMoveData(*tempGroupNames, *gGroupNames, groupNamesSize);
  816.         MyDisposeHandle(tempGroupNames);
  817.         tempGroupNames = nil;
  818.         EndCriticalMemorySequence(savedCriticalSeq);
  819.     }
  820.     return err;
  821. }
  822.  
  823.  
  824.  
  825. /*----------------------------------------------------------------------------
  826.     MustCloseBeforeRebuildDialog 
  827.     
  828.     Present the "must close windows before rebuilding full group list" dialog.
  829.             
  830.     Exit:    function result = error code.
  831. ----------------------------------------------------------------------------*/
  832.  
  833. static OSErr MustCloseBeforeRebuildDialog (void)
  834. {
  835.     OSErr err = noErr;
  836.     DialogPtr dlg = nil;
  837.     short item;
  838.     
  839.     err = MyGetNewDialog(kMustCloseBeforeRebuildDlg, ok, cancel, &dlg);
  840.     if (err != noErr) return err;
  841.     SysBeep(0);
  842.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  843.     err = DoClose(dlg);
  844.     if (err != noErr) return err;
  845.     if (item == cancel) return userCanceledErr;
  846.     return noErr;
  847. }
  848.  
  849.  
  850.  
  851. /*----------------------------------------------------------------------------
  852.     DoRebuildFullGroupList 
  853.     
  854.     Handle the "Rebuild Full Group List" command.
  855.  
  856.     Exit:    function result = error code.
  857. ----------------------------------------------------------------------------*/
  858.  
  859. OSErr DoRebuildFullGroupList (void)
  860. {
  861.     Handle newGroupNames = nil;
  862.     long **newGroupNameOffsets = nil;
  863.     long newNumGroups;
  864.     long *x;
  865.     long i;
  866.     char *p;
  867.     OSErr err = noErr;
  868.     WindowPtr wind;
  869.     TWindowKind kind;
  870.     Boolean promptBeforeClose = true;
  871.     long groupNamesSize, groupNameOffsetsSize, memNeeded;
  872.     Handle tempGroupNames = nil;
  873.     long **tempGroupNameOffsets = nil;
  874.     Boolean fullGroupListMovedToTempMem = false;
  875.     TWindow **info;
  876.     Boolean savedCriticalSeq;
  877.     
  878.     /* Close all group windows except for the full group list window, and close
  879.        all subject windows. We must do this because group and subject window data 
  880.        structures contain offsets into the old group names gGroupNames, which is 
  881.        about to be blown away and replaced. */
  882.     
  883.     while (true) {
  884.         wind = FrontWindow();
  885.         while (wind != nil) {
  886.             kind = GetMyWindowKind(wind);
  887.             if ((kind == kGroup && wind != gFullGroupWindow) ||
  888.                 kind == kSubject) break;
  889.             wind = (WindowPtr)((WindowPeek)wind)->nextWindow;
  890.         }
  891.         if (wind == nil) break;
  892.         if (promptBeforeClose) {
  893.             err = MustCloseBeforeRebuildDialog();
  894.             if (err != noErr) goto exit;
  895.             promptBeforeClose = false;
  896.         }
  897.         err = DoClose(wind);
  898.         if (err != noErr) goto exit;
  899.     }
  900.     
  901.     /* Display the status message. */
  902.     
  903.     err = DisplayStatusMessageNumber(kStrGetFullStatusMsg);
  904.     if (err != noErr) goto exit;
  905.     
  906.     /* Check available memory. If we don't have at least 1.25 times the size
  907.        of the current full group list available in the application heap,
  908.        try to move the current full group list into temporary memory to
  909.        make room to get the new full group list. */
  910.        
  911.     if (gGroupNames != nil && gGroupNameOffsets != nil) {
  912.         groupNamesSize = MyGetHandleSize(gGroupNames);
  913.         groupNameOffsetsSize = MyGetHandleSize(gGroupNameOffsets);
  914.         memNeeded = groupNamesSize + groupNameOffsetsSize;
  915.         memNeeded += memNeeded >> 2;
  916.         if (!MemoryAvailable(memNeeded)) {
  917.             if (HaveModernTempMemory()) {
  918.                 err = MyTempNewHandle(groupNamesSize, &tempGroupNames);
  919.                 if (err != noErr) goto exit;
  920.                 err = MyTempNewHandle(groupNameOffsetsSize, &tempGroupNameOffsets);
  921.                 if (err != noErr) goto exit;
  922.                 BlockMoveData(*gGroupNames, *tempGroupNames, groupNamesSize);
  923.                 MyDisposeHandle(gGroupNames);
  924.                 gGroupNames = tempGroupNames;
  925.                 BlockMoveData(*gGroupNameOffsets, *tempGroupNameOffsets, groupNameOffsetsSize);
  926.                 MyDisposeHandle(gGroupNameOffsets);
  927.                 gGroupNameOffsets = tempGroupNameOffsets;
  928.                 if (gFullGroupWindow != nil) {
  929.                     info = (TWindow**)GetWRefCon(gFullGroupWindow);
  930.                     (**info).groupNameOffsets = gGroupNameOffsets;
  931.                     (**info).numGroups = gNumGroups;
  932.                 }
  933.                 fullGroupListMovedToTempMem = true;
  934.             } else {
  935.                 return memFullErr;
  936.             }
  937.         }
  938.     }
  939.     
  940.     /* Get a list of all group names from the server. */
  941.     
  942.     err = GetGroupNames(0, &newGroupNames, &newNumGroups);
  943.     if (err != noErr) goto exit;
  944.  
  945.     /* Allocate the new array of group name offsets. */
  946.  
  947.     err = MyNewHandle(newNumGroups * sizeof(long), &newGroupNameOffsets);
  948.     if (err != noErr) goto exit;
  949.     
  950.     /* Initialize the new array of group name offsets. */
  951.     
  952.     for (i = 0, x = *newGroupNameOffsets, p = *newGroupNames; i < newNumGroups; i++, x++) {
  953.         *x = p - *newGroupNames;
  954.         p += strlen(p) + 1;
  955.     }
  956.     
  957.     /* Sort the new array of group name offsets. */
  958.     
  959.     err = SortFullGroupList(newGroupNameOffsets, newNumGroups, newGroupNames);
  960.     if (err != noErr) goto exit;
  961.     
  962.     /* Make the new array of group name offsets and group names the real ones. Dispose the old
  963.        ones. */
  964.        
  965.     MyDisposeHandle(gGroupNameOffsets);
  966.     gGroupNameOffsets = newGroupNameOffsets;
  967.     newGroupNameOffsets = nil;
  968.     gNumGroups = newNumGroups;
  969.     MyDisposeHandle(gGroupNames);
  970.     gGroupNames = newGroupNames;
  971.     newGroupNames = nil;
  972.     fullGroupListMovedToTempMem = false;
  973.     
  974.     /* Update the full group list window and return. */
  975.     
  976.     gPrefs.maxGroupNameWidth = 0;
  977.     err = UpdateFullGroupWindow();
  978.     if (err != noErr) goto exit;
  979.     GetDateTime(&gPrefs.groupCheckTime);
  980.     return noErr;
  981.     
  982. exit:
  983.  
  984.     MyDisposeHandle(newGroupNameOffsets);
  985.     MyDisposeHandle(newGroupNames);
  986.     if (fullGroupListMovedToTempMem) {
  987.         BeginCriticalMemorySequence(&savedCriticalSeq);
  988.         MyNewHandle(groupNamesSize, &gGroupNames);
  989.         BlockMoveData(*tempGroupNames, *gGroupNames, groupNamesSize);
  990.         MyNewHandle(groupNameOffsetsSize, &gGroupNameOffsets);
  991.         BlockMoveData(*tempGroupNameOffsets, *gGroupNameOffsets, groupNameOffsetsSize);
  992.         (**info).groupNameOffsets = gGroupNameOffsets;
  993.         (**info).numGroups = gNumGroups;
  994.         EndCriticalMemorySequence(savedCriticalSeq);
  995.     }
  996.     MyDisposeHandle(tempGroupNames);
  997.     MyDisposeHandle(tempGroupNameOffsets);
  998.     return err;
  999. }
  1000.